﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Web.Mvc;
using System.Web;
using Moq;
using System.Web.Script.Serialization;
using System.Xml.Serialization;
using System.IO;
using System.Text;
using System.Xml;
using MvcApi;

namespace Unit_Tests
{
    [TestClass()]
    public class RestifyAttributeTest
    {
        [TestMethod()]
        public void JsonOnActionExecuted()
        {
            var restify = new ApiAttribute();
            var controllerContext = new Mock<ControllerContext>();            
            var executedContext = MockHelpers.ActionExecutedContext(new[] { "text/json" }, controllerContext);

            var expected = (new JavaScriptSerializer()).Serialize(executedContext.Object.Controller.ViewData.Model);
            controllerContext.Setup(c => c.HttpContext.Response.Write(expected)).Verifiable();

            restify.OnActionExecuted(executedContext.Object);            
            executedContext.Object.Result.ExecuteResult(executedContext.Object.Controller.ControllerContext);

            controllerContext.Verify();
        }

        [TestMethod()]
        public void XmlOnActionExecuted()
        {
            var restify = new ApiAttribute();
            var controllerContext = new Mock<ControllerContext>();
            controllerContext.SetupGet(c => c.HttpContext.Response.Output).Returns(new StringWriter());
            var executedContext = MockHelpers.ActionExecutedContext(new[] { "text/xml" }, controllerContext);

            var builder = new StringBuilder();

            using (var stream = new StringWriter(builder))
            {
                var writer = XmlWriter.Create(stream, new XmlWriterSettings() { OmitXmlDeclaration = true });

                (new XmlSerializer(executedContext.Object.Controller.ViewData.Model.GetType()))
                    .Serialize(writer, executedContext.Object.Controller.ViewData.Model, new XmlSerializerNamespaces(new[] { new XmlQualifiedName("", "") }));
            }

            var expected = builder.ToString();

            restify.OnActionExecuted(executedContext.Object);
            executedContext.Object.Result.ExecuteResult(controllerContext.Object);

            Assert.AreEqual(expected, controllerContext.Object.HttpContext.Response.Output.ToString());
        }

        [TestMethod()]
        public void JsonOnActionExecutingAsUrlEncodedFormPost()
        {
            var restify = new ApiAttribute();
            var executingContext = MockHelpers.ActionExecutingContext("application/x-www-form-urlencoded; text/json");

            var expected = SimpleModel.Create();
            var input = string.Format("model={0}", HttpUtility.UrlEncode(expected.ToJson()));
            var inputStream = (Stream)new MemoryStream(UTF8Encoding.UTF8.GetBytes(input));

            executingContext.SetupGet(r => r.HttpContext.Request.InputStream).Returns(inputStream);

            restify.OnActionExecuting(executingContext.Object);

            Assert.AreEqual(expected.ToJson(), executingContext.Object.ActionParameters["model"].ToJson());
        }

        [TestMethod()]
        public void JsonOnActionExecutingAsUrlEncodedFormPostWithPrimitiveParameter()
        {
            var restify = new ApiAttribute();
            var expectedPrimitive = "this is a primitive";
            var executingContext = MockHelpers.ActionExecutingContext("application/x-www-form-urlencoded; text/json", expectedPrimitive);

            var expectedModel = SimpleModel.Create();
            var input = string.Format("model={0}&primitive={1}", HttpUtility.UrlEncode(expectedModel.ToJson()), HttpUtility.UrlEncode(expectedPrimitive));
            var inputStream = (Stream)new MemoryStream(UTF8Encoding.UTF8.GetBytes(input));

            executingContext.SetupGet(r => r.HttpContext.Request.InputStream).Returns(inputStream);

            restify.OnActionExecuting(executingContext.Object);

            Assert.AreEqual(expectedModel.ToJson(), executingContext.Object.ActionParameters["model"].ToJson());
            Assert.AreEqual(expectedPrimitive, executingContext.Object.ActionParameters["primitive"]);
        }

        [TestMethod(), ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void JsonOnActionExecutingWithPrimitiveParameter()
        {
            var restify = new ApiAttribute();
            var expectedPrimitive = "this is a primitive";
            var executingContext = MockHelpers.ActionExecutingContext("text/json", expectedPrimitive);

            var expectedModel = SimpleModel.Create();
            var input = expectedModel.ToJson();
            var inputStream = (Stream)new MemoryStream(UTF8Encoding.UTF8.GetBytes(input));

            executingContext.SetupGet(r => r.HttpContext.Request.InputStream).Returns(inputStream);

            restify.OnActionExecuting(executingContext.Object);

            Assert.AreEqual(expectedModel.ToJson(), executingContext.Object.ActionParameters["model"].ToJson());
            Assert.AreEqual(expectedPrimitive, executingContext.Object.ActionParameters["primitive"]);
        }

        [TestMethod()]
        public void JsonOnActionExecutingWithPrimitiveParameterWithApiBind()
        {
            var restify = new ApiAttribute();
            var expectedPrimitive = "this is a primitive";
            var executingContext = MockHelpers.ActionExecutingContext("text/json", expectedPrimitive, true);

            var expectedModel = SimpleModel.Create();
            var input = expectedModel.ToJson();
            var inputStream = (Stream)new MemoryStream(UTF8Encoding.UTF8.GetBytes(input));

            executingContext.SetupGet(r => r.HttpContext.Request.InputStream).Returns(inputStream);

            restify.OnActionExecuting(executingContext.Object);

            Assert.AreEqual(expectedModel.ToJson(), executingContext.Object.ActionParameters["model"].ToJson());
            Assert.AreEqual(expectedPrimitive, executingContext.Object.ActionParameters["primitive"]);
        }

        [TestMethod()]
        public void JsonOnActionExecuting()
        {
            var restify = new ApiAttribute();
            var executingContext = MockHelpers.ActionExecutingContext("text/json");

            var expected = SimpleModel.Create();
            var input = expected.ToJson();
            var inputStream = (Stream)new MemoryStream(UTF8Encoding.UTF8.GetBytes(input));

            executingContext.SetupGet(r => r.HttpContext.Request.InputStream).Returns(inputStream);

            restify.OnActionExecuting(executingContext.Object);

            Assert.AreEqual(typeof(SimpleModel), executingContext.Object.ActionParameters["model"].GetType());
            Assert.AreEqual(expected.ToJson(), executingContext.Object.ActionParameters["model"].ToJson());
        }

        [TestMethod()]
        public void XmlOnActionExecutingAsUrlEncodedFormPost()
        {
            var restify = new ApiAttribute();
            var executingContext = MockHelpers.ActionExecutingContext("text/xml; application/x-www-form-urlencoded");

            var expected = SimpleModel.Create();
            var input = string.Format("model={0}", HttpUtility.UrlEncode(expected.ToXml()));
            var inputStream = (Stream)new MemoryStream(UTF8Encoding.UTF8.GetBytes(input));

            executingContext.SetupGet(r => r.HttpContext.Request.InputStream).Returns(inputStream);

            restify.OnActionExecuting(executingContext.Object);

            Assert.AreEqual(expected.ToJson(), executingContext.Object.ActionParameters["model"].ToJson());
        }

        [TestMethod()]
        public void XmlOnActionExecutingAsUrlEncodedFormPostWithPrimitiveParameter()
        {
            var restify = new ApiAttribute();
            var expectedPrimitive = "this is a primitive";
            var executingContext = MockHelpers.ActionExecutingContext("text/xml; application/x-www-form-urlencoded", expectedPrimitive);

            var expectedModel = SimpleModel.Create();            
            var input = string.Format("model={0}&primitive={1}", HttpUtility.UrlEncode(expectedModel.ToXml()), HttpUtility.UrlEncode(expectedPrimitive));
            var inputStream = (Stream)new MemoryStream(UTF8Encoding.UTF8.GetBytes(input));

            executingContext.SetupGet(r => r.HttpContext.Request.InputStream).Returns(inputStream);

            restify.OnActionExecuting(executingContext.Object);

            Assert.AreEqual(expectedModel.ToJson(), executingContext.Object.ActionParameters["model"].ToJson());
            Assert.AreEqual(expectedPrimitive, executingContext.Object.ActionParameters["primitive"]);
        }

        [TestMethod()]
        public void XmlOnActionExecuting()
        {
            var restify = new ApiAttribute();
            var executingContext = MockHelpers.ActionExecutingContext("text/xml");

            var expected = SimpleModel.Create();
            var input = expected.ToXml();
            var inputStream = (Stream)new MemoryStream(UTF8Encoding.UTF8.GetBytes(input));

            executingContext.SetupGet(r => r.HttpContext.Request.InputStream).Returns(inputStream);

            restify.OnActionExecuting(executingContext.Object);

            Assert.AreEqual(expected.ToJson(), executingContext.Object.ActionParameters["model"].ToJson());
        }   
    }
}
